import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
    StyleSheet,
    View,
    Text,
    Animated,
    Dimensions,
    TouchableWithoutFeedback,
    Easing,
    Keyboard,
} from 'react-native';

const TOAST_MAX_WIDTH = 0.8;
const TOAST_ANIMATION_DURATION = 200;

const positions = {
    TOP: 20,
    BOTTOM: -20,
    CENTER: 0,
};

const durations = {
    LONG: 3500,
    SHORT: 2000,
};

let styles = StyleSheet.create({
    defaultStyle: {
        position: 'absolute',
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
        justifyContent: 'center',
        alignItems: 'center',
    },
    containerStyle: {
        padding: 10,
        backgroundColor: '#000',
        opacity: 0.8,
        borderRadius: 5,
    },
    shadowStyle: {
        shadowColor: '#000',
        shadowOffset: {
            width: 4,
            height: 4,
        },
        shadowOpacity: 0.8,
        shadowRadius: 6,
        elevation: 10,
    },
    textStyle: {
        fontSize: 16,
        color: '#fff',
        textAlign: 'center',
    },
});

class ToastContainer extends Component {
    static displayName = 'ToastContainer';

    static propTypes = {
        containerStyle: PropTypes.object,
        duration: PropTypes.number,
        visible: PropTypes.bool,
        position: PropTypes.number,
        animation: PropTypes.bool,
        shadow: PropTypes.bool,
        keyboardAvoiding: PropTypes.bool,
        backgroundColor: PropTypes.string,
        opacity: PropTypes.number,
        shadowColor: PropTypes.string,
        textColor: PropTypes.string,
        textStyle: PropTypes.object,
        hideOnPress: PropTypes.bool,
        onPress: PropTypes.func,
        onHidden: PropTypes.func,
        onShown: PropTypes.func,
        onDestroy: PropTypes.func,
    };

    static defaultProps = {
        visible: false,
        duration: durations.SHORT,
        animation: true,
        shadow: false,
        position: positions.BOTTOM,
        opacity: 0.85,
        hideOnPress: true,
        keyboardAvoiding: true,
    };

    constructor() {
        super(...arguments);
        const window = Dimensions.get('window');
        this.state = {
            visible: this.props.visible,
            opacity: new Animated.Value(0),
            windowWidth: window.width,
            windowHeight: window.height,
            keyboardScreenY: window.height,
        };
    }

    componentDidMount = () => {
        this._dimListener = Dimensions.addEventListener('change', this._windowChanged);
        if (this.props.keyboardAvoiding) {
            Keyboard.addListener('keyboardDidChangeFrame', this._keyboardDidChangeFrame);
        }
        if (this.state.visible) {
            this._show();
        }
    };

    componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS) {
        if (this.props.visible !== prevProps.visible) {
            if (this.props.visible) {
                this._show();
            } else {
                this._hide();
            }
            this.setState({
                visible: this.props.visible,
            });
        }
    }

    componentWillUnmount = () => {
        this._hide();
        this._dimListener.remove();
        Keyboard.removeAllListeners('keyboardDidChangeFrame');
    };

    _root = null;
    _keyboardHeight = 0;
    _animating = false;
    _hideTimeout = null;

    _windowChanged = ({window}) => {
        this.setState({
            windowWidth: window.width,
            windowHeight: window.height,
        });
    };

    _keyboardDidChangeFrame = ({endCoordinates}) => {
        this.setState({
            keyboardScreenY: endCoordinates.screenY,
        });
    };

    _show = () => {
        clearTimeout(this._showTimeout);
        if (!this._animating) {
            this._animating = true;
            this._root.setNativeProps({
                pointerEvents: 'auto',
            });
            Animated.timing(this.state.opacity, {
                toValue: this.props.opacity,
                duration: this.props.animation ? TOAST_ANIMATION_DURATION : 0,
                easing: Easing.out(Easing.ease),
                useNativeDriver: true,
            }).start(({finished}) => {
                if (finished) {
                    this._animating = !finished;
                    if (this.props.duration > 0) {
                        this._hideTimeout = setTimeout(() => {
                            this._hide();
                            this.props.onShown && this.props.onShown();
                        }, this.props.duration);
                    }
                }
            });
        }
    };

    _hide = () => {
        if (!this._animating) {
            if (this._root) {
                this._root.setNativeProps({
                    pointerEvents: 'none',
                });
            }
            Animated.timing(this.state.opacity, {
                toValue: 0,
                duration: this.props.animation ? TOAST_ANIMATION_DURATION : 0,
                easing: Easing.in(Easing.ease),
                useNativeDriver: true,
            }).start(({finished}) => {
                if (finished) {
                    this._animating = false;
                    this.props.onHidden && this.props.onHidden();
                    this.props.onDestroy && this.props.onDestroy();
                }
            });
        }
    };

    render() {
        let {props} = this;
        const {windowWidth, visible} = this.state;
        let offset = props.position;

        const {windowHeight, keyboardScreenY} = this.state;
        const keyboardHeight = Math.max(windowHeight - keyboardScreenY, 0);
        let position = offset ? {
            [offset < 0 ? 'bottom' : 'top']: offset < 0 ? (keyboardHeight - offset) : offset,
        } : {
            top: 0,
            bottom: keyboardHeight,
        };

        return visible ? <View
            style={[
                styles.defaultStyle,
                position,
            ]}
            pointerEvents="box-none"
        >
            <TouchableWithoutFeedback
                onPress={() => {
                    typeof this.props.onPress === 'function' ? this.props.onPress() : null;
                    this.props.hideOnPress ? this._hide() : null;
                }}
            >
                <Animated.View
                    style={[
                        styles.containerStyle,
                        {marginHorizontal: windowWidth * ((1 - TOAST_MAX_WIDTH) / 2)},
                        props.containerStyle,
                        props.backgroundColor && {backgroundColor: props.backgroundColor},
                        {
                            opacity: this.state.opacity,
                        },
                        props.shadow && styles.shadowStyle,
                        props.shadowColor && {shadowColor: props.shadowColor},
                    ]}
                    pointerEvents="none"
                    ref={ele => this._root = ele}
                >
                    <Text style={[
                        styles.textStyle,
                        props.textStyle,
                        props.textColor && {color: props.textColor},
                    ]}>
                        {this.props.children}
                    </Text>
                </Animated.View>
            </TouchableWithoutFeedback>
        </View> : null;
    }
}

export {
    positions,
    durations,
};

export default ToastContainer;
